# Copyright (C) 2007 Laszlo Pandy <laszlok2@gmail.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

import decimal
Dec = decimal.Decimal
import datetime
Date = datetime.date
import bisect

import Bill

class BillsList:
	
	SHARED_ALL, SHARED_EXACT, SHARED_ANY = range(3)
	
	def __init__(self, iterable=None):
		if iterable:
			self.bills = list(iterable)
			self.bills.sort()
		else:
			self.bills = []
		
	def __iter__(self):
		return iter(self.bills)
	
	def __contains__(self, bill):
		index = bisect.bisect_left(self.bills, bill)
		if index < len(self.bills) and self.bills[index] == bill:
			return True
		return False
	
	def __cmp__(self, billsList):
		if isinstance(billsList, BillsList):
			return cmp(self.bills, billsList.bills)
		return -1
	
	def __getitem__(self, index):
		return self.bills.__getitem__(index)
	
	def __len__(self):
		return self.bills.__len__()
	
	def copy(self):
		b = BillsList()
		b.bills = self.bills[:]
		return b
	
	def deep_copy(self):
		b = BillsList()
		b.bills = [bill.copy() for bill in self.bills]
		return b
	
	def add(self, bill):
		index = bisect.bisect_left(self.bills, bill)	
		if index >= len(self.bills):
			self.bills.append(bill)
		elif self.bills[index] != bill:
			self.bills.insert(index, bill)
			
	def extend(self, billsList):
		for bill in billsList:
			self.add(bill)
	
	def remove(self, bill):
		self.bills.remove(bill)
		
	def resort(self):
		self.bills.sort()
		
	def getYearList(self):
		if not self.bills:
			return []
		yearOne = self.bills[0].getDate().year
		yearTwo = self.bills[-1].getDate().year
		return range(yearOne, yearTwo + 1)
	
	#############################
	###  Filter Functions  ###
	
	def filterByValue(self, lower, upper):
		if type(lower) == float or type(upper) == float:
			raise TypeError("cannot compare using floats")
		
		bL = [bill for bill in self if not (lower <= bill.value <= upper)]
		[self.remove(bill) for bill in bL]
	
	def filterByDate(self, lower=None, upper=None):
		if not self.bills:
			return
		
		if not lower:
			lower = Date.min
		if not upper:
			upper = Date.max
		
		billLower = self.bills[0].copy()
		billLower.setDate(lower)
		billLower.setValue("-Infinity") # change the value because it will be compared if the dates are equal
		billUpper = self.bills[0].copy()
		billUpper.setDate(upper)
		billUpper.setValue("Infinity") # change the value because it will be compared if the dates are equal
		
		lowerIndex = bisect.bisect_left(self.bills, billLower)
		del self.bills[:lowerIndex]
		
		upperIndex = bisect.bisect_right(self.bills, billUpper)
		del self.bills[upperIndex:]
	
	def filterByPaidBy(self, payeeList):
		bL = [bill for bill in self if not bill.paidBy in payeeList]
		[self.remove(bill) for bill in bL]
	
	def filterBySharedBy(self, sharers, sharedType=SHARED_ALL):
		shareFuncs = {
			self.SHARED_ALL   : lambda sBy, sSet: sBy.issubset(sSet),
			self.SHARED_EXACT : lambda sBy, sSet: sBy == sSet,
			self.SHARED_ANY   : lambda sBy, sSet: bool(sBy.intersection(sSet))
		}
		
		bL = []
		for bill in self:
			sh = bill.getSharedBy()
			if not sh or not shareFuncs[sharedType](set(sh), set(sharers)):
				bL.append(bill)
		
		[self.remove(bill) for bill in bL]
	
	def filterByCategory(self, categoryList):
		bL = [bill for bill in self if not bill.category in categoryList]
		[self.remove(bill) for bill in bL]
	
	def filterByDetails(self, tokenList):
		bL = [b for b in self if not self._tokensInString(b.details, tokenList)]
		[self.remove(bill) for bill in bL]
	
	def filterByComments(self, tokenList):
		bL = [b for b in self if not self._tokensInString(b.comments, tokenList)]
		[self.remove(bill) for bill in bL]
	
	def filterByAnything(self, tokenList):
		bL = []
		for bill in self:
			for token in tokenList:
				t = token.lower()
				string = bill.comments + bill.details
				string += str(bill.value) + str(bill.date)
				if bill.getPaidBy():
					string += str(bill.getPaidBy())
				if bill.getSharedBy():
					string += "".join((str(x) for x in bill.getSharedBy()))
				if bill.getCategory():
					string += bill.getCategory().getName()
				
				if not token.lower() in string.lower():
					bL.append(bill)
					break
		
		[self.remove(bill) for bill in bL]
		
	def filterByYearAndMonth(self, yearInt, monthInt=None):
		if not self.bills:
			return
		
		billLower = self.bills[0].copy()
		billLower.setValue("-Infinity") # change the value because it will be compared if the dates are equal
		billUpper = self.bills[0].copy()
		billUpper.setValue("-Infinity") # change the value because it will be compared if the dates are equal
		if monthInt is None:
			billLower.setDate(Date(yearInt, 1, 1))
			billUpper.setDate(Date(yearInt + 1, 1, 1))
		else:
			billLower.setDate(Date(yearInt, monthInt, 1))
			#if we are at the end of the year, wrap to next year
			if monthInt == 12:
				billUpper.setDate(Date(yearInt + 1, 1, 1))
			else:
				billUpper.setDate(Date(yearInt, monthInt + 1, 1))
		
		lowerIndex = bisect.bisect_left(self.bills, billLower)
		del self.bills[:lowerIndex]
		
		upperIndex = bisect.bisect_left(self.bills, billUpper)
		del self.bills[upperIndex:]
		
	def filterByMonth(self, monthInt):
		bL = []
		for bill in self.bills:
			if bill.getDate().month != monthInt:
				bL.append(bill)
				
		[self.remove(bill) for bill in bL]
		
	def _tokensInString(string, tokenList):
		for token in tokenList:
			if token.lower() in string.lower():
				return True
		return False
	
	#############################
	###  Calculate Functions  ###
	
	def totalPeopleShared(self):
		totals = {}
		zero = Dec(0)
		for bill in self:
			shared = bill.getSharedBy()
			if shared:
				shareDict = shared.calculateShares(bill.value)
				for person, value in shareDict.iteritems():
					totals[person] = totals.get(person, zero) + value
		return totals
		
	def totalPeoplePaid(self):
		totals = {}
		zero = Dec(0)
		for bill in self:
			person = bill.getPaidBy()
			if person:
				totals[person] = totals.get(person, zero) + bill.value
		return totals
	
	def totalPersonShared(self, person):
		total = Dec(0)
		for bill in self:
			shared = bill.getSharedBy()
			if shared:
				total += shared.calculatePersonShare(person, bill.value)
		return total
			
	def totalPersonPaid(self, person):
		total = Dec(0)
		for bill in self:
			if person == bill.getPaidBy():
				total += bill.value
		return total
